import random
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_refresh_token_payload, get_current_user_token
from app.core.config import settings
from app.core.database import get_db
from app.core.security import create_token_pair, verify_token
from app.crud import (
    user as user_crud,
    patient_profile as patient_profile_crud,
    doctor_profile as doctor_profile_crud,
    verification_code as verification_code_crud,
    crud_file,
)
from app.crud.crud_dictionary import dict_data as dict_data_crud
from app.models.user import (
    User,
    UserType,
    VerificationStatus,
)
from app.models.verification_code import VerificationPurpose
from app.schemas.base import UnifiedResponse, success_response
from app.schemas.token import Token, TokenPayload, LoginResponse
from app.schemas.user import (
    User as UserSchema,
    UserLogin,
    PatientRegisterRequest,
    DoctorRegisterRequest,
    SmsCodeRequest,
    PatientProfileCreate,
    DoctorProfileCreate,
    UserCreate,
)
from app.utils.exceptions import (
    InvalidCredentialsException,
    UserNotFoundException,
    UserInactiveException,
    InvalidTokenException,
    UserAlreadyExistsException,
    ValidationException,
)
from app.utils.redis_client import redis_client

router = APIRouter()

SMS_CODE_TTL_MINUTES = 10
SESSION_TTL_SECONDS = 30 * 24 * 60 * 60  # 30 days

DOCTOR_HOSPITAL_DICT_TYPE = "doctor_hospital"
DOCTOR_DEPARTMENT_DICT_TYPE = "doctor_department"
DOCTOR_TITLE_DICT_TYPE = "doctor_title"


def _generate_sms_code(length: int = 6) -> str:
    return "".join(str(random.randint(0, 9)) for _ in range(length))


async def _ensure_unique_identifiers(
    db: AsyncSession,
    *,
    phone: str,
    document_number: Optional[str],
) -> None:
    if await user_crud.get_by_phone(db, phone=phone):
        raise UserAlreadyExistsException("phone")

    if document_number and await user_crud.get_by_document_number(db, document_number=document_number):
        raise UserAlreadyExistsException("document_number")


async def _validate_sms_code(
    db: AsyncSession,
    *,
    phone: str,
    purpose: VerificationPurpose,
    code: str,
) -> None:
    verification = await verification_code_crud.verify_code(
        db,
        phone=phone,
        purpose=purpose,
        code=code,
    )
    if not verification:
        raise ValidationException("验证码错误或已过期")

    await verification_code_crud.mark_used(db, verification_code=verification)


def _build_login_response(user: User, token: Token) -> LoginResponse:
    return LoginResponse(token=token, user=UserSchema.model_validate(user))


@router.post("/sms-code", response_model=UnifiedResponse[dict])
async def send_sms_code(
    payload: SmsCodeRequest,
    db: AsyncSession = Depends(get_db),
) -> UnifiedResponse[dict]:
    """Send verification SMS code (development mode returns the code)."""
    try:
        purpose = VerificationPurpose(payload.purpose)
    except ValueError as exc:
        raise ValidationException("验证码用途不合法") from exc

    code = _generate_sms_code()
    await verification_code_crud.create_code(
        db,
        phone=payload.phone,
        purpose=purpose,
        code=code,
        expire_minutes=SMS_CODE_TTL_MINUTES,
    )

    response_data = {
        "expires_in": SMS_CODE_TTL_MINUTES * 60,
    }
    if settings.DEBUG:
        response_data["code"] = code

    return success_response(response_data, msg="验证码已发送")


@router.post("/register/patient", response_model=UnifiedResponse[UserSchema])
async def register_patient(
    payload: PatientRegisterRequest,
    db: AsyncSession = Depends(get_db),
) -> UnifiedResponse[UserSchema]:
    """Register a patient account."""
    await _ensure_unique_identifiers(
        db,
        phone=payload.phone,
        document_number=payload.document_number,
    )

    await _validate_sms_code(
        db,
        phone=payload.phone,
        purpose=VerificationPurpose.REGISTER,
        code=payload.sms_code,
    )

    user_in = UserCreate(
        phone=payload.phone,
        password=payload.password,
        user_type=UserType.PATIENT,
        verification_status=VerificationStatus.APPROVED,
        is_active=True,
        is_superuser=False,
        full_name=payload.full_name,
        document_type=payload.document_type,
        document_number=payload.document_number,
        gender=payload.gender,
        birth_date=payload.birth_date,
        agreed_at=datetime.utcnow() if payload.agree_protocol else None,
    )

    user = await user_crud.create(db, obj_in=user_in)

    profile_payload = payload.profile
    if profile_payload:
        profile_data = profile_payload.model_dump(exclude_unset=True)
        height = profile_data.get("height_cm")
        weight = profile_data.get("weight_kg")
        if height and weight and height > 0:
            profile_data["bmi"] = round(weight / ((height / 100) ** 2), 1)
        profile_data["user_id"] = user.id
        await patient_profile_crud.create(
            db,
            obj_in=PatientProfileCreate(**profile_data),
        )

    return success_response(UserSchema.model_validate(user), msg="患者注册成功")


@router.post("/register/doctor", response_model=UnifiedResponse[UserSchema])
async def register_doctor(
    payload: DoctorRegisterRequest,
    db: AsyncSession = Depends(get_db),
) -> UnifiedResponse[UserSchema]:
    """Register a doctor account (manual review required)."""
    await _ensure_unique_identifiers(
        db,
        phone=payload.phone,
        document_number=payload.document_number,
    )

    await _validate_sms_code(
        db,
        phone=payload.phone,
        purpose=VerificationPurpose.REGISTER,
        code=payload.sms_code,
    )

    user_in = UserCreate(
        phone=payload.phone,
        password=payload.password,
        user_type=UserType.DOCTOR,
        verification_status=VerificationStatus.PENDING,
        is_active=True,
        is_superuser=False,
        full_name=payload.full_name,
        document_type=payload.document_type,
        document_number=payload.document_number,
        gender=payload.gender,
        birth_date=payload.birth_date,
        agreed_at=datetime.utcnow() if payload.agree_protocol else None,
    )

    user = await user_crud.create(db, obj_in=user_in)

    async def ensure_dict_value(dict_type: str, value: str, label: str) -> None:
        dict_record = await dict_data_crud.get_by_value(
            db,
            dict_type=dict_type,
            dict_value=value,
        )
        if not dict_record or dict_record.status != "0":
            raise ValidationException(f"{label}无效，请重新选择")

    await ensure_dict_value(DOCTOR_HOSPITAL_DICT_TYPE, payload.hospital_code, "医疗机构")
    await ensure_dict_value(DOCTOR_DEPARTMENT_DICT_TYPE, payload.department_code, "科室")
    await ensure_dict_value(DOCTOR_TITLE_DICT_TYPE, payload.title_code, "职称")

    profile_data = DoctorProfileCreate(
        user_id=user.id,
        qualification_certificate_no=payload.qualification_certificate_no,
        registration_certificate_no=payload.registration_certificate_no,
        hospital_code=payload.hospital_code,
        department_code=payload.department_code,
        title_code=payload.title_code,
    )
    profile = await doctor_profile_crud.create(db, obj_in=profile_data)

    if payload.certificate_file_ids:
        valid_file_ids = []
        for file_id in payload.certificate_file_ids:
            file_record = await crud_file.get(db, id=file_id)
            if not file_record:
                raise ValidationException(f"证件文件不存在: {file_id}")
            if file_record.file_type != 3:
                raise ValidationException("证件文件类型不正确")
            await crud_file.update(db, db_obj=file_record, obj_in={"user_id": user.id})
            valid_file_ids.append(file_id)

        if valid_file_ids:
            await doctor_profile_crud.add_certificate_files(
                db,
                profile=profile,
                file_ids=valid_file_ids,
            )

    message = "医生注册成功，请等待审核"
    return success_response(UserSchema.model_validate(user), msg=message)


@router.post("/login", response_model=UnifiedResponse[LoginResponse])
async def login(
    user_login: UserLogin,
    db: AsyncSession = Depends(get_db),
) -> UnifiedResponse[LoginResponse]:
    """
    Login by phone or document number with password.
    """
    db_user = await user_crud.get_by_identifier(
        db,
        identifier=user_login.identifier,
        user_type=user_login.user_type,
    )

    if not db_user:
        raise InvalidCredentialsException()

    if not user_crud.is_active(db_user):
        raise UserInactiveException()

    if not user_crud.authenticate(db_user, user_login.password):
        raise InvalidCredentialsException()

    tokens = create_token_pair(db_user.id)
    await user_crud.update_last_login(db, db_obj=db_user)

    session_data = {
        "user_id": str(db_user.id),
        "phone": db_user.phone,
        "user_type": db_user.user_type.value,
        "login_time": datetime.utcnow().isoformat(),
    }
    await redis_client.store_user_session(
        str(db_user.id),
        session_data,
        SESSION_TTL_SECONDS if user_login.remember_me else SESSION_TTL_SECONDS // 2,
    )

    login_response = _build_login_response(db_user, Token(**tokens))
    return success_response(login_response)


@router.post("/refresh", response_model=UnifiedResponse[Token])
async def refresh_token(
    db: AsyncSession = Depends(get_db),
    token_payload: TokenPayload = Depends(get_refresh_token_payload),
) -> UnifiedResponse[Token]:
    """
    Refresh access token using refresh token.
    """
    user_id = token_payload.sub
    if not user_id:
        raise InvalidTokenException()

    db_user = await user_crud.get(db, id=user_id)
    if not db_user:
        raise UserNotFoundException()

    if not user_crud.is_active(db_user):
        raise UserInactiveException()

    old_jti = token_payload.jti
    if old_jti:
        exp = token_payload.exp
        if exp:
            ttl = max(0, exp - int(datetime.utcnow().timestamp()))
            await redis_client.add_to_blacklist(old_jti, ttl)

    token_pair = create_token_pair(user_id)
    return success_response(Token(**token_pair))


@router.post("/logout", response_model=UnifiedResponse)
async def logout(
    current_user: User = Depends(get_current_user),
    token_payload: TokenPayload = Depends(get_current_user_token),
) -> UnifiedResponse:
    """
    Logout current user and blacklist tokens.
    """
    jti = token_payload.jti
    if jti:
        exp = token_payload.exp
        if exp:
            ttl = max(0, exp - int(datetime.utcnow().timestamp()))
            await redis_client.add_to_blacklist(jti, ttl)

    await redis_client.delete_user_session(current_user.id)

    return success_response(None, msg="退出登录成功")


@router.get("/me", response_model=UnifiedResponse[UserSchema])
async def read_users_me(
    current_user: User = Depends(get_current_user),
) -> UnifiedResponse[UserSchema]:
    """
    Get current user information.
    """
    return success_response(UserSchema.model_validate(current_user))
